//
// Created by HP on 2022/1/31.
//

#ifndef TMC_TMC_SHADER_H
#define TMC_TMC_SHADER_H

#include "tmc_utils.h"

// lib
#include <glad/glad.h>
#include <glm/glm.hpp>

// std
#include <string>
#include <vector>

namespace tmc {

    /**
     * Shader: 通过 RAII 进行资源管理, 构造时自动进行编译, 编译时可能会抛异常
     * **/
    class Shader {
    public:
        enum class ShaderType {
            Vert = GL_VERTEX_SHADER,
            Frag = GL_FRAGMENT_SHADER,
        };

        DELETE_COPY(Shader);

        Shader() { shaderID = 0; }
        Shader(ShaderType type, const std::string& sourceCode);
        ~Shader() { glDeleteShader(shaderID); } // A value of 0 for shader will be silently ignored

        // 实现移动语义
        Shader(Shader&& other) noexcept {
            shaderID = other.shaderID;
            other.shaderID = 0;
        }
        Shader& operator=(Shader && other) noexcept {
            shaderID = other.shaderID;
            other.shaderID = 0;
            return *this;
        }
        [[nodiscard]] static Shader createShaderFromFile(ShaderType type, const std::string& filepath);
        [[nodiscard]] GLuint getShaderId() const { return shaderID; }
        [[nodiscard]] bool   isNULL() const { return shaderID == 0; }
    private:
        GLuint shaderID;
    };

    class ShaderProgram {
    public:
        DELETE_COPY(ShaderProgram);

        ShaderProgram() = default;
        ShaderProgram(Shader vert, Shader frag);
        ~ShaderProgram();

        // 实现移动语义
        ShaderProgram(ShaderProgram&& other) noexcept {
            programID = other.programID;
            other.programID = 0;
            vertShader = std::move(other.vertShader);
            fragShader = std::move(other.fragShader);
        }
        ShaderProgram& operator=(ShaderProgram && other) noexcept {
            programID = other.programID;
            other.programID = 0;
            vertShader = std::move(other.vertShader);
            fragShader = std::move(other.fragShader);
            return *this;
        }

        void setVertShader(Shader vert) { vertShader = std::move(vert); }
        void setFragShader(Shader frag) { fragShader = std::move(frag); }
        void cleanShaders() { vertShader = {}; fragShader = {}; }
        void compileShaderProgram();
        void use();
        void setBool(const std::string &name, const bool value) const;
        void setInt(const std::string &name, const int value) const;
        void setFloat(const std::string &name, const float value) const;
        void setVec3(const std::string& name, const glm::vec3& value) const;
        void setMat4(const std::string& name, const glm::mat4& value) const;
    private:
        static void handleError(GLint result);
        Shader vertShader{};
        Shader fragShader{};
        GLuint programID{0};
    };
}

#endif //TMC_TMC_SHADER_H
